/*
 * TicTacToePlugin.java
 *
 * Created on February 8, 2005, 2:34 AM
 */

package net.jxta.myjxta.plugins.tictactoe;

import net.jxta.endpoint.ByteArrayMessageElement;
import net.jxta.endpoint.Message;
import net.jxta.endpoint.MessageElement;
import net.jxta.endpoint.StringMessageElement;
import net.jxta.logging.Logging;
import net.jxta.myjxta.dialog.Dialog;
import net.jxta.myjxta.dialog.DialogListener;
import net.jxta.myjxta.dialog.DialogMessage;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.FileImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.*;
import java.util.List;
import java.util.Timer;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author Jeff Moore
 */
public class TicTacToeGameControl implements DialogListener, MoveListener {


    /**
     * Time to wait for response after a command  message is sent
     * out. Default is 10 seconds.
     */
    private static final long DEFAULT_MESSAGE_ACK_TIMEOUT = 160000;

    /**
     * Minimum time we will wait till a message ack comes back
     */
    private static final long MINIMUM_MESSAGE_ACK_TIMEOUT = 2000;
    /**
     * Timeout waiting for config request
     */
    private static final long CONFIG_WAIT_TIMOUT = 20000;


    /**
     * Tag for an element element carrying voice data
     */
    public static final String TAG_POSITION_DATA = "PositionData";

    public static final String TAG_ICON_DATA = "IconData";

    public static final String TAG_ICON_TYPE = "IconType";

    /**
     * Element tag denoting a message containing TTT session command data
     */
    public static final String TAG_SESSION_COMMAND = "TTTSessionCommand";

    /**
     * commands sent btn peers managing the TTT session
     */

    private static final int CONNECT_SLEEP_TIME = 200;

    public static final String COMMAND_INVITE_REQUEST = "InviteRequest";

    public static final String COMMAND_INVITE_ACCEPT = "InviteAccept";

    public static final String COMMAND_START_GAME_REQUEST = "StartGameRequest";

    public static final String COMMAND_START_GAME_ACCEPT = "StartGameAccept";

    public static final String COMMAND_CONFIG_REQUEST = "ConfigRequest";

    public static final String COMMAND_CONFIG_ACCEPT = "ConfigAccept";

    public static final String COMMAND_NEW_MOVE = "NewMove";

    public static final String COMMAND_NEW_MOVE_ACCEPT = "NewMoveAccept";

    public static final String COMMAND_END_GAME_REQUEST = "EndGameRequest";

    public static final String COMMAND_END_GAME_ACCEPT = "EndGameAccept";

    public static final String COMMAND_DISCONNECT_REQUEST = "DisconnectRequest";

    public static final String COMMAND_DISCONNECT_ACCEPT = "DisconnectAccept";


    /**
     * internal session state
     */


    public static final int SESSION_DISCONNECTED = 10;

    public static final int SESSION_DISCONNECTING = 11;

    public static final int SESSION_CONNECTED = 12;

    public static final int SESSION_CONNECTING = 13;

    public static final int SESSION_STARTING = 20;

    public static final int SESSION_STARTED = 30;

    public static final int SESSION_ENDING = 40;

    public static final int SESSION_ENDED = 50;

    public static final int SESSION_PLAYING = 100;


    public static final int SESSION_DISCONNECT_REQUEST_SENT = 210;

    public static final int SESSION_DISCONNECT_REQUEST_RECEIVED = 220;

    public static final int SESSION_DISCONNECT_ACCEPT_SENT = 230;

    public static final int SESSION_DISCONNECT_ACCEPT_RECEIVED = 240;

    public static final int SESSION_START_REQUEST_SENT = 340;

    public static final int SESSION_START_REQUEST_RECEIVED = 350;

    public static final int SESSION_START_ACCEPT_SENT = 360;

    public static final int SESSION_CONFIG_REQUEST_SENT = 362;

    public static final int SESSION_CONFIG_REQUEST_RECEIVED = 363;

    public static final int SESSION_CONFIG_ACCEPT_SENT = 364;

    public static final int SESSION_CONFIG_ACCEPT_RECEIVED = 365;

    public static final int SESSION_START_ACCEPT_RECEIVED = 370;

    public static final int SESSION_END_ACCEPT_RECEIVED = 380;

    public static final int SESSION_END_ACCEPT_SENT = 390;

    public static final int SESSION_END_REQUEST_RECEIVED = 410;

    public static final int SESSION_END_REQUEST_SENT = 420;

    public static final int SESSION_INVITE_REQUEST_SENT = 430;

    public static final int SESSION_INVITE_REQUEST_RECEIVED = 440;

    public static final int SESSION_INVITE_ACCEPT_SENT = 450;

    public static final int SESSION_INVITE_ACCEPT_RECEIVED = 460;

    public int sessionState = SESSION_DISCONNECTED;

    public int protocolState = SESSION_DISCONNECTED;

    private long messageAckTimeout = DEFAULT_MESSAGE_ACK_TIMEOUT;

    private MessageAckThread messageAckThread = null;

    private boolean locallyInitiated = false;
    private DialogMessage templateMessage = null;
    private TicTacToeDialogView tttView = null;

    /**
     * in bytes
     */
    public final static long MAX_ICON_SIZE = 60000;

    public final static int REMOTE_PLAYER = 2;

    public final static int LOCAL_PLAYER = 1;


    public final static int GAME_OVER = 1;
    public final static int GAME_WAITING = 2;
    public final static int GAME_IN_PLAY = 3;
    public final static int GAME_LOST = 4;
    public final static int GAME_WON = 5;
    public final static int GAME_DRAW = 6;


    public final static String SESSION_ICON_EXCHANGE = "iconExchange";
    private int remotePlayerTotalWins = 0;
    private int localPlayerTotalWins = 0;
    private int gameState = GAME_OVER;
    private int playersTurn = 0;
    private final List<String> localPlayerMoves ;
    private final List<String> remotePlayerMoves;
    private List<String> allMoves = null;
    private Object localIconChosenLock = null;
    private String remotePlayerName = null;
    private Hashtable<Integer, String> sessionStateTable = null;
    private Dialog tttDialog = null;
    static final Logger LOG = Logger.getLogger(TicTacToeGameControl.class.getName());
    private boolean configured = false;
    private TimerTask configWaitTimerTask = null;
    private Timer generalTimer = null;

    private String[] winningSet = null;

    private static final String[] draw = {
            "A0", "A1", "A2",
            "B0", "B1", "B2",
            "C0", "C1", "C2"
    };

    private static final String[][] wins = {
            {"A0", "A1", "A2"},
            {"B0", "B1", "B2"},
            {"C0", "C1", "C2"},
            {"A0", "B0", "C0"},
            {"A1", "B1", "C1"},
            {"A2", "B2", "C2"},
            {"A0", "B1", "C2"},
            {"A2", "B1", "C0"}
    };


    /**
     * Creates a new instance of TicTacToePlugin
     */
    public TicTacToeGameControl(final TicTacToeDialogView tttView, final Dialog tttDialog) {

        LOG.setLevel(Level.INFO);

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("TicTacToeGameCOntrol constructor");

        }
        locallyInitiated = tttView.isLocallyInitiated();
        this.sessionStateTable = new Hashtable<Integer, String>();
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_CONFIG_ACCEPT_RECEIVED), "SESSION_CONFIG_ACCEPT_RECEIVED");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_CONFIG_ACCEPT_SENT), "SESSION_CONFIG_ACCEPT_SENT");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_CONFIG_REQUEST_RECEIVED), "SESSION_CONFIG_REQUEST_RECEIVED");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_CONFIG_REQUEST_SENT), "SESSION_CONFIG_REQUEST_SENT");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_CONNECTED), "SESSION_CONNECTED");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_CONNECTING), "SESSION_CONNECTING");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_DISCONNECTED), "SESSION_DISCONNECTED");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_DISCONNECTING), "SESSION_DISCONNECTING");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_DISCONNECT_ACCEPT_RECEIVED), "SESSION_DISCONNECT_ACCEPT_RECEIVED");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_DISCONNECT_ACCEPT_SENT), "SESSION_DISCONNECT_ACCEPT_SENT");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_DISCONNECT_REQUEST_RECEIVED), "SESSION_DISCONNECT_REQUEST_RECEIVED");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_ENDED), "SESSION_ENDED");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_DISCONNECT_REQUEST_SENT), "SESSION_DISCONNECT_REQUEST_SENT");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_ENDING), "SESSION_ENDING");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_PLAYING), "SESSION_PLAYING");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_INVITE_ACCEPT_RECEIVED), "SESSION_INVITE_ACCEPT_RECEIVED");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_INVITE_ACCEPT_SENT), "SESSION_INVITE_ACCEPT_SENT");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_INVITE_REQUEST_RECEIVED), "SESSION_INVITE_REQUEST_RECEIVED");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_INVITE_REQUEST_SENT), "SESSION_INVITE_REQUEST_SENT");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_STARTED), "SESSION_STARTED");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_STARTING), "SESSION_STARTING");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_START_ACCEPT_RECEIVED), "SESSION_START_ACCEPT_RECEIVED");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_START_ACCEPT_SENT), "SESSION_START_ACCEPT_SENT");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_START_REQUEST_RECEIVED), "SESSION_START_REQUEST_RECEIVED");
        this.sessionStateTable.put(new Integer(TicTacToeGameControl.SESSION_START_REQUEST_SENT), "SESSION_START_REQUEST_SENT");


        this.tttView = tttView;

        this.tttDialog = tttDialog;

        this.localIconChosenLock = new Object();

        this.templateMessage = new DialogMessage(this.tttDialog.getGroup()
                .getPeerGroup().getPeerName(), null, this.tttDialog.getGroup()
                .getPeerGroup().getPeerGroupID().toString(), this.tttDialog
                .getGroup().getPeerGroup().getPeerGroupName());

        this.localPlayerMoves = new ArrayList<String>();

        this.remotePlayerMoves = new ArrayList<String>();

        this.allMoves = new ArrayList<String>();

        this.tttView.addMoveListener(this);

        this.tttDialog.addListener(this);

        // wait for pipes to connect
        if (this.tttDialog != null) {
            new Thread(new Runnable() {

                public void run() {
                    while (!TicTacToeGameControl.this.tttDialog.isConnected()) {
                        try {
                            Thread.sleep(CONNECT_SLEEP_TIME);
                            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                                LOG.info("Waiting for is connected ");
                            }
                        }
                        catch (final InterruptedException ignored) {
                        }
                    }
                }
            }, getClass().getName() + ":isConnected").start();
        }

        this.generalTimer = new Timer(true);


    }

    public String getSessionStateString(final int sessionState) {

        return this.sessionStateTable.get(new Integer(sessionState));
    }

    public void initSession() {
        if (isLocallyInitiated()) {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("locally inititated");
            }

            this.setProtocolState(TicTacToeGameControl.SESSION_INVITE_REQUEST_SENT);
            this.sendCommand(TicTacToeGameControl.COMMAND_INVITE_REQUEST, TicTacToeGameControl.COMMAND_INVITE_ACCEPT);


            //generalTimer.schedule(roundTripTimerTask, this.ROUND_TRIP_INTERVAL, this.ROUND_TRIP_INTERVAL);
        } else {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("NOT locally inititated");
            }
        }
    }

    public void gameControl(final String command, final DialogMessage msg) {


        if (COMMAND_INVITE_REQUEST.equals(command)) {
            //call should be in disconnected state for this to happen

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("gameControl : received " +
                        TicTacToeGameControl.COMMAND_INVITE_REQUEST +
                        " received");
            }
            if (getProtocolState() == TicTacToeGameControl.SESSION_DISCONNECTED) {

                setProtocolState(TicTacToeGameControl.SESSION_INVITE_REQUEST_RECEIVED);

                this.sendCommand(TicTacToeGameControl.COMMAND_INVITE_ACCEPT);

                setProtocolState(TicTacToeGameControl.SESSION_INVITE_ACCEPT_SENT);


            } else {
                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("expected " +
                            TicTacToeGameControl.SESSION_DISCONNECTED + " got " +
                            command);
                }
            }

        } else if (COMMAND_INVITE_ACCEPT.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("gameControl : received " +
                        TicTacToeGameControl.COMMAND_INVITE_ACCEPT +
                        " received");
            }


            if (getProtocolState() == TicTacToeGameControl.SESSION_INVITE_REQUEST_SENT) {

                updateAckThread(command);

                setProtocolState(TicTacToeGameControl.SESSION_INVITE_ACCEPT_RECEIVED);

                //we wait on user to config and send config out

            } else {
                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("expected " +
                            TicTacToeGameControl.SESSION_INVITE_REQUEST_SENT +
                            " got " + command);
                }
            }

        } else if (COMMAND_CONFIG_REQUEST.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("gameControl : received " +
                        TicTacToeGameControl.COMMAND_CONFIG_REQUEST +
                        " received");
            }


            if (getProtocolState() == TicTacToeGameControl.SESSION_INVITE_ACCEPT_SENT) {

                parseConfigMessage(msg);

                setProtocolState(TicTacToeGameControl.SESSION_CONFIG_REQUEST_RECEIVED);

                localActionConfigRequestReceived(msg);

                // wait on user to send config accept

            } else {
                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("expected " +
                            TicTacToeGameControl.SESSION_INVITE_ACCEPT_SENT +
                            " - got " + command);
                }
            }

        } else if (COMMAND_CONFIG_ACCEPT.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("gameControl : received " +
                        TicTacToeGameControl.COMMAND_CONFIG_ACCEPT +
                        " received");
            }


            if (getProtocolState() == TicTacToeGameControl.SESSION_CONFIG_REQUEST_SENT) {


                updateAckThread(command);

                parseConfigMessage(msg);

                setProtocolState(TicTacToeGameControl.SESSION_CONFIG_ACCEPT_RECEIVED);

                localActionConfigAcceptReceived(msg);


                setProtocolState(TicTacToeGameControl.SESSION_START_REQUEST_SENT);
                sendCommand(TicTacToeGameControl.COMMAND_START_GAME_REQUEST, TicTacToeGameControl.COMMAND_START_GAME_ACCEPT);


                /** remote player will make first move */

            } else {
                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("expected " +
                            TicTacToeGameControl.SESSION_CONFIG_REQUEST_SENT +
                            " got " + command);
                }
            }

        } else if (COMMAND_START_GAME_REQUEST.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("gameControl : received " +
                        TicTacToeGameControl.COMMAND_START_GAME_REQUEST +
                        " received");
            }


            if ((getProtocolState() == TicTacToeGameControl.SESSION_CONFIG_ACCEPT_SENT) ||
                    (getProtocolState() == TicTacToeGameControl.SESSION_ENDED)) {

                setProtocolState(TicTacToeGameControl.SESSION_START_REQUEST_RECEIVED);
                //do any processing before we accept to start the voice convo

                boolean success = sendCommand(TicTacToeGameControl.COMMAND_START_GAME_ACCEPT);

                setProtocolState(TicTacToeGameControl.SESSION_START_ACCEPT_SENT);

                setProtocolState(TicTacToeGameControl.SESSION_PLAYING);

                /** remote player will make first move */

                this.localPlayerMoves.clear();
                this.remotePlayerMoves.clear();
                this.allMoves.clear();

                setPlayersTurn(TicTacToeGameControl.REMOTE_PLAYER);

            } else {
                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("expected " +
                            TicTacToeGameControl.SESSION_CONFIG_ACCEPT_SENT +
                            " got " + command);
                }
            }

        } else if (COMMAND_START_GAME_ACCEPT.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("gameControl : received " +
                        TicTacToeGameControl.COMMAND_START_GAME_ACCEPT +
                        " received");
            }

            if (getProtocolState() == TicTacToeGameControl.SESSION_START_REQUEST_SENT) {

                updateAckThread(command);

                this.localPlayerMoves.clear();
                this.remotePlayerMoves.clear();
                this.allMoves.clear();

                // this is the last command received before starting voice
                // transmission/reception. signal thread to start sending TTTdata
                setProtocolState(TicTacToeGameControl.SESSION_START_ACCEPT_RECEIVED);


                setProtocolState(TicTacToeGameControl.SESSION_PLAYING);

                setPlayersTurn(TicTacToeGameControl.LOCAL_PLAYER);
                /* local player makes first move */
                //let the ui pick up this event

            } else {
                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("expected " +
                            TicTacToeGameControl.SESSION_START_REQUEST_SENT +
                            " got " + command);
                }
            }

        } else if (COMMAND_END_GAME_REQUEST.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("gameControl : received " +
                        TicTacToeGameControl.COMMAND_END_GAME_REQUEST +
                        " received");
            }


            if (getProtocolState() == TicTacToeGameControl.SESSION_PLAYING) {

                setProtocolState(TicTacToeGameControl.SESSION_END_REQUEST_RECEIVED);

                sendCommand(TicTacToeGameControl.COMMAND_END_GAME_ACCEPT);

                setProtocolState(TicTacToeGameControl.SESSION_END_ACCEPT_SENT);

                setProtocolState(TicTacToeGameControl.SESSION_ENDED);

                localActionEndGame();

            } else {
                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("expected " + TicTacToeGameControl.SESSION_PLAYING +
                            " got " + command);
                }
            }

        } else if (COMMAND_END_GAME_ACCEPT.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("gameControl : received " +
                        TicTacToeGameControl.COMMAND_END_GAME_ACCEPT +
                        " received");
            }


            if (getProtocolState() == TicTacToeGameControl.SESSION_END_REQUEST_SENT) {

                updateAckThread(command);
                setProtocolState(TicTacToeGameControl.SESSION_END_ACCEPT_RECEIVED);

                //hang up call


                setProtocolState(TicTacToeGameControl.SESSION_ENDED);

            } else {
                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("expected " +
                            TicTacToeGameControl.SESSION_END_REQUEST_SENT +
                            " got " + command);
                }
            }

        } else if (COMMAND_DISCONNECT_REQUEST.equals(command)) {


            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("gameControl : receive " +
                        TicTacToeGameControl.COMMAND_DISCONNECT_REQUEST +
                        " received");
            }

            // this is not garunteed... need to change
            if (getProtocolState() == getProtocolState()) {//this.SESSION_ENDED) {

                setProtocolState(TicTacToeGameControl.SESSION_DISCONNECT_REQUEST_RECEIVED);

                sendCommand(TicTacToeGameControl.COMMAND_DISCONNECT_ACCEPT);

                setProtocolState(TicTacToeGameControl.SESSION_DISCONNECT_ACCEPT_SENT);

                //disconnect from host - close window no more calls from this tab
                localActionEndSession();

                setProtocolState(TicTacToeGameControl.SESSION_DISCONNECTED);

            } else {
                // the other side is going down... kill session
                // TODO:

                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("expected SESSION_TTT_ENDED - got " + command);
                }
            }
        } else if (COMMAND_DISCONNECT_ACCEPT.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("gameControl : received " + command + " received");
            }

            if (getProtocolState() == getProtocolState()) {//this.SESSION_ENDED) {

                updateAckThread(command);

                setProtocolState(TicTacToeGameControl.SESSION_DISCONNECT_ACCEPT_RECEIVED);

                //disconnect from host - close window no more calls from this tab
                localActionEndSession();

                setProtocolState(TicTacToeGameControl.SESSION_DISCONNECTED);

            } else {
                // the other side is going down... kill session
                // TODO:

                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("expected SESSION_TTT_ENDED - got " + command);
                }
            }
        } else {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.warning("got strange command " + command + " is local:"+isLocallyInitiated());
            }

        }

        LOG.setLevel(Level.INFO);

    }

    private boolean sendCommand(final String command) {

        return sendCommand(command, null, null);
    }

    private void sendCommand(final String command, final String ackCommand) {

        sendCommand(command, ackCommand, null);
    }

    /**
     * Sends a command on the vijxta dialog pipe. This does not change the
     * Protocol state. We have to remember to do this ourselves.
     */
    private boolean sendCommand(final String command, final String ackCommand, final HashMap<String, MessageElement> elementMap) {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("sendCommand : " + command);
        }


        final DialogMessage msg = (DialogMessage) this.templateMessage.clone();

        final StringMessageElement commandElement = new StringMessageElement(
                TAG_SESSION_COMMAND, command, null);

        msg.addMessageElement(TAG_SESSION_COMMAND, commandElement);

        if (elementMap != null) {

            final Iterator<String> keys = elementMap.keySet().iterator();

            for (String elementName : elementMap.keySet()) {

                final MessageElement element = elementMap.get(elementName);

                msg.addMessageElement(elementName, element);

            }
        }

        if (ackCommand != null) {
            if (this.messageAckThread==null){
                this.messageAckThread = new MessageAckThread(ackCommand);

                this.messageAckThread.start();

                this.messageAckThread.waitForStart();
            } else {
                System.out.println("ack thread should be null!");
            }
        }

        return this.tttDialog.dispatch(msg);

    }

    /**
     * message has been received... determine if its the correct response
     */
    protected void updateAckThread(final String command) {
        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("Checking AckThread");
        }

        if (this.messageAckThread != null) {

            this.messageAckThread.setMessageAcknowledged(command);

            if (this.messageAckThread.isThreadEnded()) {

                this.messageAckThread = null;
            }
        } else {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("MessageAckThread is null!!");
            }

        }
    }

    /**
     * This method updates the current protocol state. Also signals the UI
     * of a protocol state change.
     */
    private void setProtocolState(final int protocolState) {

        this.protocolState = protocolState;

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("ProtocolStateChanged to " +
                    this.sessionStateTable.get(new Integer(protocolState)));
        }

        this.tttView.protocolStateChanged(protocolState);
    }

    /**
     * Returns the protocol state
     */
    public int getProtocolState() {

        return this.protocolState;
    }

    /**
     * Returns the current time in milliseconds to wait for a message respose
     * before session shutdown.
     */
    public long getMessageAckTimeout() {

        return (this.messageAckTimeout > TicTacToeGameControl.MINIMUM_MESSAGE_ACK_TIMEOUT) ?
                this.messageAckTimeout : DEFAULT_MESSAGE_ACK_TIMEOUT;
    }

    public void setMessageAckTimeout(final long timeInMilliseconds) {

        if (timeInMilliseconds > MINIMUM_MESSAGE_ACK_TIMEOUT) {

            this.messageAckTimeout = timeInMilliseconds;
        }
    }

    /**
     * Message ack thread has timed out and no message ack has been received.
     * End session.
     */
    protected void localActionMessageGoneUnAcknowledged() {
        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info(" localActionMessageGoneUnAcknowledged ");
        }

        localActionEndSession();

        /** Normally we'd just call sendcommand(END_SESSION) and wait for a
         *  end END_SEESION_ACCEPT then go down gracefully. Since we aren't
         *  receiving command messages we choose to just exit hard. */

        sendCommand(TicTacToeGameControl.COMMAND_DISCONNECT_REQUEST);

        this.setProtocolState(TicTacToeGameControl.SESSION_CONNECTED);
    }

    private void localActionEndSession() {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("localActionEndSession");
        }


    }

    public int getRemotePlayerTotalWins() {
        return this.remotePlayerTotalWins;
    }

    public int getLocalPlayerTotalWins() {
        return this.localPlayerTotalWins;
    }

    protected void setRemotePlayerTotalWins(final int remotePlayerTotalWins) {
        this.remotePlayerTotalWins = remotePlayerTotalWins;
    }

    protected void setLocalPlayerTotalWins(final int localPlayerTotalWins) {
        this.localPlayerTotalWins = localPlayerTotalWins;
    }


    public void viewActionNewGame() {

        if (getProtocolState() == TicTacToeGameControl.SESSION_ENDED) {

            sendCommand(TicTacToeGameControl.COMMAND_START_GAME_REQUEST, TicTacToeGameControl.COMMAND_START_GAME_ACCEPT);

            setProtocolState(TicTacToeGameControl.SESSION_START_REQUEST_SENT);

        }
    }

    private void setPlayersTurn(final int player) {

        this.playersTurn = player;

        this.tttView.setCurrentPlayersTurn(player);
    }

    public int getPlayersTurn() {

        return this.playersTurn;

    }

    public void destruct() {

        this.tttDialog.removeListener(this);

        sendCommand(TicTacToeGameControl.COMMAND_DISCONNECT_REQUEST);

        setProtocolState(TicTacToeGameControl.SESSION_DISCONNECTED);


    }

    /**
     * not pretty or speedy but ..eh for now
     */
    private int getRemotePlayerBoardState() {
        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("start getremotePlayerBoardState");
        }


        if (this.allMoves.containsAll(Arrays.asList(TicTacToeGameControl.draw))) {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("getremotePlayerBoardState GAME_DRAW");
            }
            return TicTacToeGameControl.GAME_DRAW;
        }

        for (String[] element : TicTacToeGameControl.wins) {

            if (this.remotePlayerMoves.containsAll(Arrays.asList(element))) {

                this.winningSet = element;

                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("getRemotePlayerBoardState GAME_WON");
                }
                return TicTacToeGameControl.GAME_WON;
            }
        }
        return GAME_IN_PLAY;

    }


    private int getLocalPlayerBoardState() {
        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("start getlocalplayerboradstate");
        }


        if (this.allMoves.containsAll(Arrays.asList(TicTacToeGameControl.draw))) {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("getLocalPlayerBoardState GAME_DRAW");
            }
            return TicTacToeGameControl.GAME_DRAW;
        }

        for (String[] element : TicTacToeGameControl.wins) {

            if (this.localPlayerMoves.containsAll(Arrays.asList(element))) {

                this.winningSet = element;
                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("getLocalPlayerBoardState GAME_WON");
                }
                return TicTacToeGameControl.GAME_WON;
            }
        }


        System.out.println("end getlocalplayerboradstate");
        return TicTacToeGameControl.GAME_IN_PLAY;
    }

    private void dispatchMove(final String moveId) {


        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("Dispatch Move : " + moveId);
        }

        final DialogMessage msg = (DialogMessage) this.templateMessage.clone();

        final StringMessageElement commandElement = new StringMessageElement(
                TicTacToeGameControl.TAG_SESSION_COMMAND, TicTacToeGameControl.COMMAND_NEW_MOVE, null);

        msg.addMessageElement(TicTacToeGameControl.TAG_SESSION_COMMAND, commandElement);

        final StringMessageElement positionElement = new StringMessageElement(
                TicTacToeGameControl.TAG_POSITION_DATA, moveId, null);

        msg.addMessageElement(TicTacToeGameControl.TAG_POSITION_DATA, positionElement);

        this.tttDialog.dispatch(msg);


    }


    /**
     * callback from the view that local player has made a move, the ui has
     * already been updated
     * -the order of method calls is important here
     */
    public void localMoveMade(final String moveId) {

        synchronized (this.localPlayerMoves) {
            this.localPlayerMoves.add(moveId);
            this.allMoves.add(moveId);
        }

        /** send move to remote peer */
        dispatchMove(moveId);

        final Object[] p = this.localPlayerMoves.toArray();
        for (Object element : p) {
            System.out.print(element);
        }
        System.out.println("");

        final int boardState = getLocalPlayerBoardState();

        if (boardState == TicTacToeGameControl.GAME_DRAW) {

            this.setPlayersTurn(TicTacToeGameControl.SESSION_ENDED);


            setGameState(TicTacToeGameControl.GAME_DRAW);

            this.tttView.setGameDraw();

            localActionEndGame();


        }
        if (boardState == TicTacToeGameControl.GAME_WON) {

            localActionEndGame();

            this.setLocalPlayerTotalWins(this.localPlayerTotalWins + 1);

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("GameWon localPlayerWins : " +
                        this.localPlayerTotalWins);
            }

            setGameState(TicTacToeGameControl.GAME_WON);

            this.tttView.setLocalPlayerWon(this.winningSet);

            this.winningSet = null;

            setPlayersTurn(TicTacToeGameControl.SESSION_ENDED);


        } else {
            setPlayersTurn(TicTacToeGameControl.REMOTE_PLAYER);
        }


    }

    private void setGameState(final int gameStatus) {
        this.gameState = gameStatus;
    }

    /**
     * called when a message comes in from a remtoe peer
     */
    private void remoteMoveMade(final String moveId) {

        synchronized (this.remotePlayerMoves) {
            this.remotePlayerMoves.add(moveId);
            this.allMoves.add(moveId);
        }

        /** send remote move to UI */
        this.tttView.remoteMoveMade(moveId);

        final int boardState = getRemotePlayerBoardState();

        if (boardState == TicTacToeGameControl.GAME_DRAW) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("GameLost remotePlayerWins : " +
                        this.remotePlayerTotalWins);
            }

            setGameState(TicTacToeGameControl.GAME_DRAW);

            this.tttView.setGameDraw();

            localActionEndGame();


        } else if (boardState == TicTacToeGameControl.GAME_WON) {
            //relative to the remote player
            setGameState(TicTacToeGameControl.GAME_LOST);

            this.setRemotePlayerTotalWins(this.remotePlayerTotalWins + 1);

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("GameLost remotePlayerWins : " +
                        this.remotePlayerTotalWins);
            }

            this.tttView.setLocalPlayerLost(this.winningSet);

            this.winningSet = null;

            localActionEndGame();


        } else {
            setPlayersTurn(TicTacToeGameControl.LOCAL_PLAYER);
        }

    }

    protected void localActionEndGame() {

        if (isLocallyInitiated()) {

            sendCommand(TicTacToeGameControl.COMMAND_END_GAME_REQUEST, TicTacToeGameControl.COMMAND_END_GAME_ACCEPT);

            setProtocolState(TicTacToeGameControl.SESSION_END_REQUEST_SENT);

            setPlayersTurn(TicTacToeGameControl.SESSION_ENDED);

            this.localPlayerMoves.clear();
            this.remotePlayerMoves.clear();
            this.allMoves.clear();
        }

    }


    public void localActionInvitePeer() {

        if (this.getProtocolState() == TicTacToeGameControl.SESSION_DISCONNECTED) {

            sendCommand(TicTacToeGameControl.COMMAND_INVITE_REQUEST, TicTacToeGameControl.COMMAND_INVITE_ACCEPT);

            setProtocolState(TicTacToeGameControl.SESSION_INVITE_REQUEST_SENT);

        }

    }

    public boolean isLocallyInitiated() {

        return locallyInitiated;
    }


    protected int getGameState() {

        return this.gameState;
    }

    class GameEvent {

//        private String action = null;
//        private String originator = null;
//        private String moveId = null;

        private final static String ELEMENT_HEADER = "Jxta:TicTacToe";
        private final static String ELEMENT_ACTION = ELEMENT_HEADER + "Action";
        private final static String ELEMENT_ORIGINATOR = ELEMENT_HEADER + "Originator";
        private final static String ELEMENT_MOVEID = ELEMENT_HEADER + "MoveId";


        public GameEvent() {
        }

        public GameEvent(final Message m) {
//            this.action = getElement (m, ELEMENT_ACTION);
//            this.originator = getElement (m, ELEMENT_ORIGINATOR);
//            this.moveId = getElement (m, ELEMENT_MOVEID);

        }

        protected String getElement(final Message msg, final String tag) {

            final MessageElement me = msg.getMessageElement(tag);
            return me != null ? me.toString() : null;

        }


        public Message toMessage() {

            return null;
        }

    }

    /**
     * Called from the UI. This methodnotify's GameControl the user accepted
     * an invite to play.
     */

    public void viewActionAcceptInviteRequest() {

        sendCommand(TicTacToeGameControl.COMMAND_INVITE_ACCEPT);

        setProtocolState(TicTacToeGameControl.SESSION_INVITE_ACCEPT_SENT);


    }

    public String getRemotePlayerName() {

        return this.remotePlayerName;

    }

    protected void localActionConfigAcceptReceived(final DialogMessage msg) {

    }


    protected void localActionConfigRequestReceived(final DialogMessage msg) {
        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("localActionConfigRequestReceived");
        }
        if (this.configWaitTimerTask != null) {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("localActionConfigRequestReceived: cancel configWaitTimerTask");
            }
            this.configWaitTimerTask.cancel();
        }

        if (this.isConfigured()) {

            localActionSendConfigAccept();
        } else {
            //we wait for user to configure
        }
    }

    private void parseConfigMessage(final DialogMessage msg) {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("localActionReceivedIconCommand");
        }

        final MessageElement iconTypeElement = msg.getMessageElement(TicTacToeGameControl.TAG_ICON_TYPE);

        this.tttView.setRemotePlayerName(msg.getOriginator());

        if (iconTypeElement != null) {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("iconTypeElement is not null");
            }


            if (true) {


                final String iconType = iconTypeElement.toString();

                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("icontype is " + iconType);
                }

                if (iconType.equals(TicTacToeDialogView.ICON_TYPE_X)) {

                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info("iconType is iconX");
                    }

                    this.tttView.setRemotePlayerIconType(TicTacToeDialogView.ICON_TYPE_X);

                    this.tttView.setRemotePlayerIcon(this.tttView.getIconX());

                } else if (iconType.equals(TicTacToeDialogView.ICON_TYPE_O)) {

                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info("iconType is iconO");
                    }

                    this.tttView.setRemotePlayerIconType(TicTacToeDialogView.ICON_TYPE_O);

                    this.tttView.setRemotePlayerIcon(this.tttView.getIconO());

                } else if (iconType.equals(TicTacToeDialogView.ICON_TYPE_CUSTOM)) {
                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info("icontype is custom");
                    }

                    final MessageElement iconDataElement = msg.getMessageElement(TicTacToeGameControl.TAG_ICON_DATA);

                    if (iconDataElement != null) {
                        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                            LOG.info("icondataelement is not null");
                        }

                        if (iconDataElement instanceof ByteArrayMessageElement) {
                            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                                LOG.info("icondata element is a bytearray message element");
                            }

                            final byte[] imageData = ((ByteArrayMessageElement) iconDataElement).getBytes();

                            this.tttView.setRemotePlayerIconType(TicTacToeDialogView.ICON_TYPE_CUSTOM);

                            this.tttView.setRemotePlayerIcon(new ImageIcon(imageData));//this.getMemImage (imageData));

                        }
                    }

                } else {
                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info("remote icontype unknown[" + iconType + "][" +
                                TicTacToeDialogView.ICON_TYPE_X + "]");
                    }
                }

            } else {
                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("icontypeelement not instance of bytearraymessageelement");
                }
            }
        } else {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("icontypeelement is null");
            }
        }

    }

    private void setConfigured(final boolean configured) {

        this.configured = configured;
    }

    private boolean isConfigured() {

        return this.configured;
    }

    public void viewActionConfigured() {

        this.setConfigured(true);

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("viewActionConfigured");
        }

        if (isLocallyInitiated()) {

            this.localActionSendConfigRequest();

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("viewActionConfigured: Locally initited");
            }

        } else {


            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("viewActionConfigured: NOT Locally initited");
            }

            if (this.getProtocolState() == SESSION_CONFIG_REQUEST_RECEIVED) {

                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("viewActionConfigured: config request has been received");
                }

                localActionSendConfigAccept();

            } else {

                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("viewActionConfigured: config request has NOT been received. starting timer");
                }

                /** we'll wait 40 second for a config to come from the controlling
                 *  peer. if none arrives then we go down. */
                this.configWaitTimerTask = new TimerTask() {

                    public void run() {
                        if (getProtocolState() ==
                                SESSION_CONFIG_REQUEST_RECEIVED) {
                            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                                LOG.info("configWaitTimerTask: CONFIG REQUEST Received");
                            }
                            localActionSendConfigAccept();
                        } else {
                            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                                LOG.info("configWaitTimerTask: Timeout wating for remote config");
                            }
                            destroy();
                        }
                    }
                };

                this.generalTimer.schedule(this.configWaitTimerTask, CONFIG_WAIT_TIMOUT);

            }


        }
    }

    public void destroy() {


        if (this.getProtocolState() != TicTacToeGameControl.SESSION_DISCONNECTED) {

            this.localActionMessageGoneUnAcknowledged();
        }


        if (this.generalTimer != null) {

            this.generalTimer.cancel();
        }
    }

    protected void localActionSendConfigAccept() {

        final HashMap<String, MessageElement> configMap = getConfigElements();

        setProtocolState(TicTacToeGameControl.SESSION_CONFIG_ACCEPT_SENT);
        sendCommand(TicTacToeGameControl.COMMAND_CONFIG_ACCEPT,
                null,
                configMap);


    }

    public Image getFileImage(final String fileName) {
        BufferedImage bufImage = null;
        try {
            final int index = fileName.lastIndexOf(".");
            final String ext = fileName.substring(index + 1, fileName.length());

            final Iterator readers = ImageIO.getImageReadersByFormatName(ext);


            final ImageReader reader = (ImageReader) readers.next();

            //File fileImage = new File("D:\\home\\Media\\my media\\PICS\\COMIC\\64X64\\head1.png");
            final File fileImage = new File(fileName);//""C:\\Documents and Settings\\polo\\Desktop\\pictures\\doubleheart2.jpg");
            final FileImageInputStream imageInputStream = new FileImageInputStream(fileImage);

            reader.setInput(imageInputStream);

            bufImage = reader.read(0);


            imageInputStream.close();
        } catch (final Exception x) {
            x.printStackTrace();
        }
        return bufImage;
    }

    public Image getMemImage(final byte[] imageBytes) {


        BufferedImage bufImage = null;

        if (imageBytes != null) {

            try {
                final Iterator readers = ImageIO.getImageReadersByFormatName("jpg");


                final ImageReader reader = (ImageReader) readers.next();

                final ByteArrayInputStream in = new ByteArrayInputStream(imageBytes);

                final MemoryCacheImageInputStream imageInputStream = new MemoryCacheImageInputStream(in);

                reader.setInput(imageInputStream);

                bufImage = reader.read(0);

                imageInputStream.close();

            } catch (final Exception x) {
                x.printStackTrace();
            }
        }

        return bufImage;
    }

    public byte[] getImageFileBytes() {

        byte[] b = null;
        try {
            final String fileName = this.tttView.getLocalPlayerIconFileName();

            final File fileImage = new File(fileName);
            if (fileImage.exists()) {
                final FileInputStream in = new FileInputStream(fileImage);
                b = new byte[in.available()];

                int bytesRead=in.read(b);
                //todo thats not correct, in.available is NOT the filesize!
            }
        } catch (final Exception x) {
            x.printStackTrace();
        }
        return b;
    }

    private HashMap<String, MessageElement> getConfigElements() {
        final HashMap<String, MessageElement> elementMap = new HashMap<String, MessageElement>();

        final StringMessageElement iconTypeElement = new StringMessageElement(
                TicTacToeGameControl.TAG_ICON_TYPE, this.tttView.getLocalPlayerIconType(), null);

        elementMap.put(TicTacToeGameControl.TAG_ICON_TYPE, iconTypeElement);

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("IconType Sent : " + this.tttView.getLocalPlayerIconType());
        }


        if (TicTacToeDialogView.ICON_TYPE_CUSTOM.equals(this.tttView.getLocalPlayerIconType())) {


            final byte[] imageBytes = this.getImageFileBytes();

            final ByteArrayMessageElement iconDataElement = new ByteArrayMessageElement(
                    TicTacToeGameControl.TAG_ICON_DATA, null, imageBytes, 0, imageBytes.length, null);

            elementMap.put(TicTacToeGameControl.TAG_ICON_DATA, iconDataElement);

        }

        return elementMap;
    }


    private void localActionSendConfigRequest() {

        this.setProtocolState(TicTacToeGameControl.SESSION_CONFIG_REQUEST_SENT);
        this.sendCommand(TicTacToeGameControl.COMMAND_CONFIG_REQUEST, TicTacToeGameControl.COMMAND_CONFIG_ACCEPT, this.getConfigElements());

    }


    public int getSessionState() {

        return this.sessionState;

    }


    private void setRemotePlayerName(final String remotePlayerName) {

        this.remotePlayerName = remotePlayerName;

    }


    public void receive(final DialogMessage msg) {


        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            //LOG.info ("Begin receive (DialogMessage)");
        }


        setRemotePlayerName(msg.getOriginator());

        //retrieve the messages command
        final String sessionCommand = getMessageSessionCommand(msg);

        // TTT data is processed and sent to audio out
        // this will be the dominant command by far so lets get this out immediately
        if (sessionCommand.equals(COMMAND_NEW_MOVE)) {

            if (getProtocolState() == SESSION_PLAYING) {

                receiveTTTPositionData(msg);
            }

        } else {

            gameControl(sessionCommand, msg);

        }
    }


    public void receiveTTTPositionData(final DialogMessage msg) {


        final String position = getMessagePositionData(msg);


        if (position != null) {

            remoteMoveMade(position);

        } else {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("reveicePositionData : position data is null");
            }
        }

    }

    /**
     * Retrieves the command (as String) from this DialogMessage. A command can
     * denote Session State and Voice Data Messages
     *
     * @param msg DialogMessage containing the command string
     * @return String command
     */
    private String getMessageSessionCommand(final DialogMessage msg) {

        String command = null;


        final MessageElement commandMessageElement = msg.getMessageElement(TicTacToeGameControl.TAG_SESSION_COMMAND);

        if (commandMessageElement != null) {
            command = commandMessageElement.toString();
        } else {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("sessionMessageCommandElement is null");
            }
        }
        if (command.equals("StartGameAccept")){
            System.out.println("StartGameAccept");
        }

        return command;
    }

    /**
     * Retrieves the data element from this DialogMessage and returns the speex
     * encoded bytes.
     *
     * @param msg The DialogMessage containing voice data
     * @return byte[] actual speex encoded bytes
     */
    private String getMessagePositionData(final DialogMessage msg) {

        String position = null;


        final MessageElement positionMessageElement = msg.getMessageElement(TicTacToeGameControl.TAG_POSITION_DATA);

        if (positionMessageElement != null) {
            position = positionMessageElement.toString();
        } else {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("sessionMessagePositionData is null");
            }
        }

        return position;

    }

    /**
     * After a message is sent this thread is instantiated, started and will
     * wait till it's lock is notified of an incoming message or till a timeout
     * is reached. The idea being each commmand message dispatched will return
     * an accept response. If that response is received then +1 if not this thread
     * will notify us and shutdown the session as gracefully as possible. A new
     * Thread is started for each command message sent.
     * TODO
     * Better option then creating a new thread for each command. Reuse of a single
     * thread maybe.
     */
    class MessageAckThread extends Thread {

        private final AckWaitLock ackWaitLock;

        private String commandName = null;

        private boolean threadEnded = false;

        private boolean threadStarted = false;

        public MessageAckThread(final String commandName) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("MessageAckThread waiting for command " + commandName);
            }

            this.ackWaitLock = new AckWaitLock();

            this.commandName = commandName;

            this.setPriority(Thread.MIN_PRIORITY);

            //this.setDaemon (true);
        }

        public void waitForStart() {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("MessageAckThread waiting for threadstart");
            }
            while (!this.threadStarted) {

                try {
                    Thread.sleep(100);
                } catch (final InterruptedException ix) {
                    ix.printStackTrace();
                }
            }
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("MessageAckThread thread started");
            }

        }

        public void run() {

            this.threadStarted = true;

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("MessageAckThread : Run ");
            }

            try {

                synchronized (this.ackWaitLock) {
                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info("MessageAckThread : waiting for"+this.commandName);
                    }
                    this.ackWaitLock.wait(getMessageAckTimeout());
                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info("MessageAckThread : DONE waiting for "+this.commandName);
                    }
                }
            } catch (final InterruptedException ix) {

                ix.printStackTrace();
            }

            synchronized (this.ackWaitLock) {

                if (!this.ackWaitLock.isAcknowledged()) {
                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info("MessageAckThread : " + this.commandName +
                                " command NOT acknowledged");
                    }
                    localActionMessageGoneUnAcknowledged();
                }
            }

            this.threadEnded = true;


        }

        /**
         * Avoids a race condition... needs to be reworked.
         */
        public boolean isThreadEnded() {

            while (!this.threadEnded) {

                try {
                    Thread.sleep(100);
                } catch (final InterruptedException ix) {
                    ix.printStackTrace();
                }
            }

            return this.threadEnded;
        }

        /**
         * A message was received and this thread is alerted.
         */
        public void setMessageAcknowledged(final String commandName) {

            synchronized (this.ackWaitLock) {

                if (this.commandName.equals(commandName)) {

                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info("MessageAckThread : Received RIGHT Ack " +
                                commandName + " for " + this.commandName +
                                " : Command Ack");
                    }

                    this.ackWaitLock.setAcknowledged(true);
                } else {

                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info("MessageAckThread : Received WRONG Ack " +
                                commandName + " for " + this.commandName +
                                " : Command Ack");
                    }
                }

                this.ackWaitLock.notifyAll();

            }

        }

        class AckWaitLock {

            private boolean acknowledged = false;

            public boolean isAcknowledged() {

                return this.acknowledged;
            }

            public void setAcknowledged(final boolean acked) {

                this.acknowledged = acked;

                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("AckWaitLock : SetAcknowldge " + this.acknowledged);
                }
            }

        }
    }


    class MyLock {

        private boolean locked = false;

        public boolean isLocked() {
            return this.locked;
        }

        public void setLocked(final boolean locked) {
            this.locked = locked;
        }
    }


}
